package com.mike.mvcframework.servlet;

import com.mike.mvcframework.annotations.*;
import com.mike.mvcframework.pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description
 * @Date 2021/7/22 7:51
 * @Created by mike
 */
public class MyDispatcherServlet extends HttpServlet {

    private final Properties properties = new Properties();

    //  classNames缓存
    private final List<String> classNameList = new ArrayList<>();

    // ioc容器
    private final Map<String, Object> ioc = new HashMap<>();

    // handlerMapping   这个用个map存储会更好,key为pattern
    private final List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) {

        // 1 加载配置文件 springmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2 扫描相关的类,扫描注解 存入classNameList
        doScan(properties.getProperty("scanPackage"));

        // 3 初始化bean对象(实现ioc容器,基于注解) 存入ioc容器中
        doInstance();

        // 4 实现依赖注入
        doAutowired();

        // 5 构建一个HandlerMapping处理器映射器,将配置好的url和method建立映射关系 , 存入 handlerMapping中
        initHandlerMapping();
        // 等待请求进入,处理请求

    }

    // 构建一个HandlerMapping处理器映射器
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(MyController.class)) {
                String baseUrl = "";
                // 看一下类上有没有 MyRequestMapping ,有的话 url中拼接上这个value
                if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl = requestMapping.value();
                }

                // 获取方法
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(MyRequestMapping.class)) {
                        MyRequestMapping methodAnnotation = method.getAnnotation(MyRequestMapping.class);

                        //  设置handler
                        Handler handler = new Handler(entry.getValue(), method, Pattern.compile(baseUrl + methodAnnotation.value()));
                        Parameter[] parameters = method.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                            Parameter parameter = parameters[i];
                            if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                                // 如果是request和response对象，那么参数名称写HttpServletRequest和HttpServletResponse
                                handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                            } else {
                                handler.getParamIndexMapping().put(parameter.getName(), i);
                            }
                        }
                        // 把类上的权限放进去
                        if (clazz.isAnnotationPresent(MySecurity.class)) {
                            MySecurity security = clazz.getAnnotation(MySecurity.class);
                            handler.getSecurityList().addAll(Arrays.asList(security.value()));
                        }
                        // 把方法上的权限放进去
                        if (method.isAnnotationPresent(MySecurity.class)) {
                            MySecurity security = method.getAnnotation(MySecurity.class);
                            handler.getSecurityList().addAll(Arrays.asList(security.value()));
                        }
                        handlerMapping.add(handler);
                    }
                }
            }
        }
    }

    // 实现依赖注入
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object obj = entry.getValue();
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(MyAutowired.class)) {
                    String beanName = field.getAnnotation(MyAutowired.class).value();
                    if (isEmpty(beanName)) {
                        beanName = field.getType().getName();
                    }
                    String substring = lowerFirst(beanName.substring(beanName.lastIndexOf(".") + 1));
                    field.setAccessible(true);
                    try {
                        field.set(entry.getValue(), ioc.get(substring));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    // 基于classNames缓存的类的全限定类名,以及反射技术完成对象创建和管理 , 存入ioc容器中
    private void doInstance() {
        if (classNameList.size() == 0) {
            return;
        }
        try {
            for (String className : classNameList) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)) {
                    MyController controller = clazz.getAnnotation(MyController.class);
                    String value = controller.value();
                    if (isEmpty(value)) {
                        value = lowerFirst(clazz.getSimpleName());
                    }
                    ioc.put(value, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    MyService service = clazz.getAnnotation(MyService.class);
                    String value = service.value();
                    if (isEmpty(value)) {
                        value = lowerFirst(clazz.getSimpleName());
                    }
                    ioc.put(value, clazz.newInstance());
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        ioc.put(lowerFirst(anInterface.getSimpleName()), clazz.newInstance());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 扫描类  com.mike.demo
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File file = new File(scanPackagePath);
        File[] files = file.listFiles();
        if (files != null) {
            for (File file1 : files) {
                String name = scanPackage + "." + file1.getName();
                if (file1.isDirectory()) {
                    doScan(name);
                } else {
                    classNameList.add(name.replace(".class", ""));
                }
            }
        }
    }

    //  加载配置文件

    private void doLoadConfig(String contextConfigLocation) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean isEmpty(String value) {
        return "".equals(value) || value == null;
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置响应编码类型
        resp.setCharacterEncoding("UTF-8");
        // 设置相应内容类型
        resp.setContentType("application/json;charset=utf-8");

        // 从handlerMapping中获取请求uri
        Handler handler = getHandler(req);

        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }

        Map<String, String[]> parameterMap = req.getParameterMap();

        // 获取类和方法上的MySecurity注解
        Set<String> securitySet = handler.getSecurityList();
        boolean hasSecurity = false;
        if (securitySet.size() > 0) {  // 权限列表长度大于0才去校验权限
            // 权限校验  访问Handler时，用户名直接以参数名username紧跟在请求的url后面即可，比如http://localhost:8080/demo/handle01?username=zhangsan
            String[] userNames = parameterMap.get("username");  // 获取userName

            // 比对userNames中有没有该handler的访问权限,有放行,没有告知无权限访问
            if (!(userNames == null || userNames.length < 0)) {
                for (String userName : userNames) {
                    if (securitySet.contains(userName)) {
                        hasSecurity = true;
                    }
                }
            }
        } else {
            hasSecurity = true;
        }

        if (!hasSecurity) {
            System.out.println("没有权限访问");
            resp.getWriter().write("403 没有权限访问");
            return;
        }

        Method method = handler.getMethod();
        Object controller = handler.getController();
        Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
        // 设置参数,参数长度为方法参数长度
        Object[] args = new Object[method.getParameters().length];

        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String combinStr = "";
            // 拼接参数,用","分割
            for (String value : entry.getValue()) {
                combinStr += "," + value;
            }
            if (paramIndexMapping.containsKey(key)) {
                Integer index = paramIndexMapping.get(key);
                args[index] = combinStr.replaceFirst(",", "");
            }
        }

        String requestName = HttpServletRequest.class.getSimpleName();
        if (paramIndexMapping.containsKey(requestName)) {
            int requestIndex = paramIndexMapping.get(requestName); // 0
            args[requestIndex] = req;
        }
        String responseName = HttpServletResponse.class.getSimpleName();
        if (paramIndexMapping.containsKey(responseName)) {
            int responseIndex = paramIndexMapping.get(responseName); // 1
            args[responseIndex] = resp;
        }

        try {
            method.invoke(controller, args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        String uri = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(uri);
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    // 首字母变小写
    private String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return new String(chars);
    }

}
